package cn.codingjc.peekaboo.application.config.limit;

import cn.codingjc.peekaboo.application.util.IPUtils;
import cn.codingjc.peekaboo.application.util.MessageUtils;
import cn.codingjc.peekaboo.domain.exception.BusinessException;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import java.lang.reflect.Method;
import java.util.Collections;
import java.util.List;

/**
 * @date: 2023/5/9
 * @author: Codingjc
 * @className: LimitFlowAspect
 **/

@Slf4j
@Aspect
@Component
public class LimitFlowAspect {

    @Autowired
    private RedisTemplate<Object, Object> redisTemplate;

    @Autowired
    private DefaultRedisScript<Long> redisScript;

    @Pointcut("@annotation(LimitFlow)")
    public void pointcut(){}

    @Around("pointcut()")
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        MethodSignature methodSignature = (MethodSignature) pjp.getSignature();
        Method method = methodSignature.getMethod();
        LimitFlow limitFlow = method.getAnnotation(LimitFlow.class);
        if (limitFlow != null) {
            int time = limitFlow.time();
            int count = limitFlow.count();
            String key = getCombineKey(limitFlow, method);
            List<Object> keys = Collections.singletonList(key);
            try {
                Long countRedis = redisTemplate.execute(redisScript, keys, count, time);
                if (countRedis == null || countRedis.intValue() > count) {
                    throw new BusinessException(MessageUtils.getMessage("trigger.current.limiting", time));
                }
                log.info("限制请求'{}',当前请求'{}',缓存key'{}'", count, countRedis.intValue(), key);
            } catch (BusinessException e) {
                throw e;
            } catch (Exception e) {
                log.error("LimitFlowAspect around error: ", e);
                throw new BusinessException(MessageUtils.getMessage("trigger.current.limiting"));
            }
        }
        return pjp.proceed();
    }

    /**
     * 获取限流key
     * @param limitFlow
     * @param method
     * @return
     */
    private String getCombineKey(LimitFlow limitFlow, Method method) {
        StringBuffer result = new StringBuffer(limitFlow.key());
        LimitType limitType = limitFlow.limitType();
        if (limitType.getType() == LimitType.IP.getType()) {
            result.append(IPUtils.getIpAddress(((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest())).append("-");
        }
        String className = method.getDeclaringClass().getName();
        String methodName = method.getName();
        result.append(className).append("-").append(methodName);
        return result.toString();
    }
}
